package com.auth.token.base;

import com.alibaba.fastjson.JSON;
import org.springframework.data.redis.core.RedisTemplate;

import java.time.Instant;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

/**
 * @author: 王霄
 * @date: 2022/1/20 18:14
 */
public class TokenRedisClient<T> implements TokenClient<T> {

    private static final String ACCESS_SECTION = "at";
    private static final String REFRESH_SECTION = "rt";

    private final TokenRedisParams tokenRedisParams;
    private final TokenGenerator tokenGenerator;
    private final RedisTemplate<String, AccessTokenEntity<?>> atRedisTemplate;
    private final RedisTemplate<String, RefreshTokenEntity<?>> rtRedisTemplate;
    private final Class<T> tClass;

    public TokenRedisClient(TokenRedisParams tokenRedisParams,
                            TokenGenerator tokenGenerator,
                            RedisTemplate<String, AccessTokenEntity<?>> atRedisTemplate,
                            RedisTemplate<String, RefreshTokenEntity<?>> rtRedisTemplate,
                            Class<T> tClass) {
        this.tokenRedisParams = tokenRedisParams;
        this.tokenGenerator = tokenGenerator;
        this.atRedisTemplate = atRedisTemplate;
        this.rtRedisTemplate = rtRedisTemplate;
        this.tClass = tClass;
    }

    @Override
    public TokenGroup askToken(T data) {
        //生成accessToken和refreshToken
        String accessToken = tokenGenerator.generate();
        String refreshToken = tokenGenerator.generate();

        //储存accessToken详细信息
        Boolean at = atRedisTemplate.opsForValue().setIfAbsent(
                getAccessKey(accessToken),
                new AccessTokenEntity<>(refreshToken, data),
                tokenRedisParams.getAccessTokenExpiration(),
                TimeUnit.SECONDS);

        //储存refreshToken详细信息
        Boolean rt = rtRedisTemplate.opsForValue().setIfAbsent(
                getRefreshKey(refreshToken),
                new RefreshTokenEntity<>(accessToken, data),
                tokenRedisParams.getRefreshTokenExpiration(),
                TimeUnit.SECONDS);

        //两者都保存成功,封装返回
        if ((at != null && at) && (rt != null && rt)) {
            return TokenGroup.builder()
                    .accessToken(accessToken)
                    .refreshToken(refreshToken)
                    .accessTokenExp(Instant.now().plusSeconds(tokenRedisParams.getAccessTokenExpiration()))
                    .build();
        }
        return null;
    }

    @Override
    public TokenGroup refreshToken(String refreshToken) {

        //根据refreshToken查询详细信息
        RefreshTokenEntity<?> entity = rtRedisTemplate.opsForValue().get(getRefreshKey(refreshToken));

        //refreshToken未过期
        if (entity != null) {

            //先生成新的accessToken,并储存
            String newAccessToken = tokenGenerator.generate();
            Boolean at = atRedisTemplate.opsForValue().setIfAbsent(
                    getAccessKey(newAccessToken),
                    new AccessTokenEntity<>(refreshToken, entity.getData()),
                    tokenRedisParams.getAccessTokenExpiration(),
                    TimeUnit.SECONDS);

            //新的accessToken保存成功
            if (at != null && at) {

                //删除旧的accessToken信息
                atRedisTemplate.delete(getAccessKey(entity.getAccessToken()));

                //更新refreshToken中的accessToken,并重置过期时间
                entity.setAccessToken(newAccessToken);
                rtRedisTemplate.opsForValue().setIfPresent(
                        getRefreshKey(refreshToken),
                        entity,
                        tokenRedisParams.getRefreshTokenExpiration(),
                        TimeUnit.SECONDS);

                return TokenGroup.builder()
                        .accessToken(newAccessToken)
                        .refreshToken(refreshToken)
                        .accessTokenExp(Instant.now().plusSeconds(tokenRedisParams.getAccessTokenExpiration()))
                        .build();
            }
        }
        return null;
    }

    @Override
    public T getData(String accessToken) {
        return Optional.of(accessToken)
                .map(this::getAccessKey)
                .map(k -> atRedisTemplate.opsForValue().get(k))
                .map(AccessTokenEntity::getData)
                .map(String::valueOf)
                .map(x -> JSON.parseObject(x, tClass))
                .orElse(null);
    }

    @Override
    public void delByAccessToken(String accessToken) {
        Optional.of(accessToken)
                .map(this::getAccessKey)
                .map(k -> atRedisTemplate.opsForValue().getAndDelete(k))
                .map(AccessTokenEntity::getRefreshToken)
                .map(this::getRefreshKey)
                .map(rtRedisTemplate::delete);
    }

    @Override
    public void delByRefreshToken(String refreshToken) {
        Optional.of(refreshToken)
                .map(this::getRefreshKey)
                .map(k -> rtRedisTemplate.opsForValue().getAndDelete(k))
                .map(RefreshTokenEntity::getAccessToken)
                .map(this::getAccessKey)
                .map(atRedisTemplate::delete);
    }

    private String getAccessKey(String key) {
        return tokenRedisParams.getRedisNamespace()
                .concat(":")
                .concat(ACCESS_SECTION)
                .concat(":")
                .concat(key);
    }

    private String getRefreshKey(String key) {
        return tokenRedisParams
                .getRedisNamespace()
                .concat(":")
                .concat(REFRESH_SECTION)
                .concat(":")
                .concat(key);
    }
}
